﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using PairingMatrixService.Contract;
using ThoughtWorksMingleLib;

namespace PairingMatrix.Web
{
    public class PairingMatrixService : IPairingMatrixService
    {
        private static readonly string DataXmlFilePath = AppDomain.CurrentDomain.BaseDirectory + "data.xml";

        #region AppData

        private AppData GetAppData()
        {
            try
            {
                lock (this)
                {
                    if (Registry.AppData != null)
                        return Registry.AppData;

                    var xmlSerializer = new XmlSerializer(typeof (AppData));
                    var streamReader = new StreamReader(DataXmlFilePath);
                    Registry.AppData = (AppData) xmlSerializer.Deserialize(streamReader);
                    streamReader.Close();
                }
            }
            catch
            {
                Registry.AppData = new AppData();
            }

            return Registry.AppData;
        }

        private void SaveData_Deprecated(AppData data)
        {
            //do nothing for time being
        }

        public void SaveData()
        {
            List<TeamMember> teamMembers = Registry.AppData.TeamMembers;
            if (teamMembers == null || teamMembers.Count == 0)
                return;

            lock (this)
            {
                while (!SaveFile(Registry.AppData))
                {
                    Thread.Sleep(500);
                }
            }
        }

        private bool SaveFile(AppData data)
        {
            StreamWriter streamWriter;
            try
            {
                string backup = AppDomain.CurrentDomain.BaseDirectory + "Data backup\\" +
                                DateTime.Now.ToString().Replace(":", "_").Replace(" ", "_").Replace("/", "_") +
                                "_data.xml";
                Log("Taking back up to " + backup);
                File.Copy(DataXmlFilePath, backup);
                var xmlSerializer = new XmlSerializer(typeof (AppData));
                streamWriter = new StreamWriter(DataXmlFilePath);
                xmlSerializer.Serialize(streamWriter, data);
                streamWriter.Close();
                Log("appData.xml saved");
                return true;
            }
            catch (Exception e)
            {
                Log(e.ToString());
                return false;
            }
        }

        private static void Log(string msg)
        {
            var logger = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "ServiceLog.txt", true);
            logger.Write(Environment.NewLine + DateTime.Now + " - " + msg);
            logger.Close();
        }

        public bool IsFileUsedbyAnotherProcess()
        {
            try
            {
                File.Open(DataXmlFilePath, FileMode.Open, FileAccess.Write, FileShare.None);
            }
            catch (IOException exp)
            {
                return true;
            }

            return false;
        }

        #endregion

        #region Project Cards

        public List<ProjectCard> GetProjectCards()
        {
            AppData appData = GetAppData();
            return appData.ProjectCards;
        }

        public ProjectCard AddProjectCard(string name)
        {
            AppData appData = GetAppData();
            int min = appData.ProjectCards.Count == 0 ? 0 : appData.ProjectCards.Min(c => c.CardNumber);
            int cardNumber = min <= 0 ? min - 1 : -1;
            if (!appData.ProjectCards.Exists(c => c.CardNumber == cardNumber))
            {
                var projectCard = new ProjectCard
                                      {CardNumber = cardNumber, Title = name, StoryStatus = "New", IsMingleCard = false};
                appData.ProjectCards.Add(projectCard);
                SaveData_Deprecated(appData);
                return projectCard;
            }

            return appData.ProjectCards.First(c => c.CardNumber == cardNumber);
        }

        public ProjectCard GetProjectCardFromMingle(int cardNumber, string username, string password)
        {
            if (cardNumber <= 0)
                return null;

            const string MINGLEHOST = "http://trainline.mingle.thoughtworks.com";
            const string STORY_STATUS_KEY = "Story Status";
            const string DEFECT_STATUS_KEY = "Defect Status";

            var mingle = new MingleServer(MINGLEHOST, username, password);
            MingleProject project = mingle.GetProject("trainline_all_workstreams");
            MingleCard card = project.GetCard(cardNumber);
            MingleCardProperty storyStatus = null;
            if (card.CardProperties.Keys.Contains(STORY_STATUS_KEY))
                storyStatus = card.CardProperties[STORY_STATUS_KEY];
            else if (card.CardProperties.Keys.Contains(DEFECT_STATUS_KEY))
                storyStatus = card.CardProperties[DEFECT_STATUS_KEY];

            double devEstimate =
                Convert.ToDouble(card.CardProperties["Dev Estimate"].Value == ""
                                     ? "0"
                                     : card.CardProperties["Dev Estimate"].Value);
            string priorityString = card.Type == "Defect"
                                        ? card.CardProperties["Defect Priority"].Value
                                        : card.CardProperties["Priority"].Value;
            int priority = 0;
            if (priorityString != "")
                priority = Convert.ToInt32(priorityString.Substring(0, 1));
            var projectCardFromMingle = new ProjectCard
                                            {
                                                CardNumber = card.Number,
                                                Title = card.Name,
                                                StoryStatus = storyStatus != null ? storyStatus.Value : "New",
                                                IsMingleCard = true,
                                                DevEstimate = devEstimate,
                                                Priority = priority
                                            };

            AppData appData = GetAppData();
            appData.ProjectCards.RemoveAll(c => c.CardNumber == cardNumber);
            appData.ProjectCards.Add(projectCardFromMingle);
            SaveData_Deprecated(appData);

            return projectCardFromMingle;
        }

        public List<ProjectCard> GetProjectCardsFromMingle(string mingleUrl, string username, string password)
        {
            if(string.IsNullOrEmpty(mingleUrl))
                return new List<ProjectCard>();

            WebRequest request = WebRequest.Create(mingleUrl);
            WebResponse response = null;
            try
            {
                response = request.GetResponse();
            }
            catch (WebException e)
            {
                var httpWebResponse = (HttpWebResponse) e.Response;
                if (httpWebResponse != null)
                {
                    string challenge = null;
                    challenge = httpWebResponse.GetResponseHeader("WWW-Authenticate");
                    if (challenge != null)
                    {
                        string auth = "Basic " +
                                      Convert.ToBase64String(Encoding.Default.GetBytes(username + ":" + password));
                        var req = WebRequest.Create(mingleUrl) as HttpWebRequest;
                        req.PreAuthenticate = true;
                        req.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
                        req.Headers.Add("Authorization", auth);
                        req.UserAgent =
                            ": Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 4.0.20506)";
                        response = req.GetResponse();
                    }
                }
            }

            Stream stream = response.GetResponseStream();
            var xmlDocument = new XmlDocument();
            xmlDocument.Load(stream);
            XmlNodeList cards = xmlDocument.GetElementsByTagName("card");
            response.Close();

            List<ProjectCard> projectCards = (from XmlNode card in cards
                                              select
                                                  new ProjectCard
                                                      {
                                                          CardNumber = Convert.ToInt32(card["number"].InnerText),
                                                          Title = card["name"].InnerText,
                                                          StoryStatus = GetCardStatus(card["properties"]),
                                                          IsMingleCard = true,
                                                          DevEstimate = GetDevEstimate(card["properties"]),
                                                          Priority = GetPriority(card["properties"])
                                                      }).ToList();


            AppData appData = GetAppData();
            projectCards.ForEach(card =>
                                     {
                                         if (!appData.ProjectCards.Any(p => p.CardNumber == card.CardNumber))
                                             appData.ProjectCards.Add(card);
                                     });
            SaveData_Deprecated(appData);
            return projectCards;
        }

        private double GetDevEstimate(XmlElement propertiesNode)
        {
            XmlNodeList properties = propertiesNode.ChildNodes;
            string devEstimateString = "";
            foreach (XmlElement property in properties)
            {
                XmlElement name = property["name"];
                if (name.InnerText == "Dev Estimate")
                {
                    devEstimateString = property["value"].InnerText;
                }
            }
            return Convert.ToDouble(devEstimateString == "" ? "0" : devEstimateString);
        }

        private int GetPriority(XmlElement propertiesNode)
        {
            XmlNodeList properties = propertiesNode.ChildNodes;
            string priorityString = "";
            foreach (XmlElement property in properties)
            {
                XmlElement name = property["name"];
                if (name.InnerText == "Defect Priority" || name.InnerText == "Priority")
                {
                    priorityString = property["value"].InnerText;
                }
            }
            if (priorityString != "")
                return Convert.ToInt32(priorityString.Substring(0, 1));
            return 0;
        }

        private string GetCardStatus(XmlElement propertiesNode)
        {
            XmlNodeList properties = propertiesNode.ChildNodes;
            foreach (XmlElement property in properties)
            {
                XmlElement name = property["name"];
                if (name.InnerText == "Story Status" || name.InnerText == "Defect Status")
                {
                    return property["value"].InnerText;
                }
            }
            return "New";
        }

        public void UpdateProjectCardDetails(int cardNumber, string title)
        {
            AppData appData = GetAppData();
            ProjectCard projectCard = appData.ProjectCards.Find(t => t.CardNumber == cardNumber);
            projectCard.Title = title;
            SaveData_Deprecated(appData);
        }

        #endregion

        #region Team Members

        public List<TeamMember> GetTeamMembers()
        {
            AppData appData = GetAppData();
            return appData.TeamMembers;
        }

        public TeamMember AddTeamMember(string name, string photoUri)
        {
            AppData appData = GetAppData();
            var teamMember = new TeamMember {Name = name, PhotoUri = photoUri};
            appData.TeamMembers.Add(teamMember);
            SaveData_Deprecated(appData);
            return teamMember;
        }

        public void UpdateTeamMemberDetails(Guid id, string name, string photoUri)
        {
            AppData appData = GetAppData();
            TeamMember teamMember = appData.TeamMembers.Find(t => t.Id == id);
            teamMember.Name = name;
            teamMember.PhotoUri = photoUri;
            SaveData_Deprecated(appData);
        }

        public void AddIceCreamToTeamMember(Guid id, DateTime date)
        {
            AppData appData = GetAppData();
            TeamMember teamMember = appData.TeamMembers.Find(t => t.Id == id);
            teamMember.IceCreams.Add(date);
            SaveData_Deprecated(appData);
        }

        public void ClearIceCreamsForTeamMember(Guid id)
        {
            AppData appData = GetAppData();
            TeamMember teamMember = appData.TeamMembers.Find(t => t.Id == id);
            teamMember.IceCreams.Clear();
            SaveData_Deprecated(appData);
        }

        public void DeleteTeamMember(Guid id)
        {
            AppData appData = GetAppData();
            appData.TeamMembers.RemoveAll(t => t.Id == id);
            SaveData_Deprecated(appData);
        }

        #endregion

        #region Card Wall Instance

        public CardWallInstanceOnADate AddCardWallInstanceOn(DateTime date)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWallInstance = null;
            if (appData.ListOfCardWallInstanceOnADate.Count != 0)
            {
                cardWallInstance = appData.ListOfCardWallInstanceOnADate.FirstOrDefault(x => x.Date.Date == date);
            }
            bool wallCreated = false;
            if (cardWallInstance == null)
            {
                cardWallInstance = new CardWallInstanceOnADate {Date = date};
                appData.ListOfCardWallInstanceOnADate.Add(cardWallInstance);
                wallCreated = true;
            }
            bool cardModified = false;

            if (wallCreated || cardModified)
            {
                SaveData_Deprecated(appData);
            }

            //doing the loop again since we do not want to save s.Card in file
            cardWallInstance.StorySignUps.ForEach(s =>
                                                      {
                                                          s.Card =
                                                              appData.ProjectCards.First(
                                                                  p => p.CardNumber == s.CardNumber);
                                                          s.Card.TotalNumberOfSignUps = appData.
                                                              ListOfCardWallInstanceOnADate
                                                              .FindAll(w => w.Date != DateTime.Today)
                                                              .SelectMany(w => w.StorySignUps)
                                                              .Count(
                                                                  signups =>
                                                                  signups.CardNumber == s.CardNumber &&
                                                                  signups.Assignees.Count > 0);
                                                      });
            return cardWallInstance;
//            catch (Exception e)
//            {
//                EventLog.WriteEntry("Application", e.ToString(), EventLogEntryType.Error);
//                throw;
//            }
        }

        private bool DoesMingleStatusMatchWithThisAppWallStatus(StorySignUp s, ProjectCard projectCardFromMingle)
        {
            if (s.StoryDevStage == projectCardFromMingle.StoryStatus)
                return true;

            switch (projectCardFromMingle.StoryStatus.Replace(" ", ""))
            {
                case "New":
                case "InPlanning":
                case "ReadyForAnalysis":
                case "InAnalysis":
                case "ReadyForDev":
                case "OnHold":
                case "Split":
                    return s.StoryDevStage == "New" ||
                           s.StoryDevStage == "InPlanning" ||
                           s.StoryDevStage == "ReadyForAnalysis" ||
                           s.StoryDevStage == "InAnalysis" ||
                           s.StoryDevStage == "ReadyForDev" ||
                           s.StoryDevStage == "OnHold" ||
                           s.StoryDevStage == "Split";

                case "InDev":
                case "InDevWaitingforBuild":
                case "ReadyForBAAcceptance":
                    return s.StoryDevStage == "InDev" ||
                           s.StoryDevStage == "InDevWaitingforBuild" ||
                           s.StoryDevStage == "ReadyForBAAcceptance";


                case "ReadyForQA":
                case "InQA":
                case "ReadyForShowcase":
                case "AwaitingFix":
                case "AwaitingReTestbyQA":
                case "AwaitingReTestbyCustomer":
                    return s.StoryDevStage == "ReadyForQA" ||
                           s.StoryDevStage == "InQA" ||
                           s.StoryDevStage == "ReadyForShowcase" ||
                           s.StoryDevStage == "AwaitingFix" ||
                           s.StoryDevStage == "AwaitingReTestbyQA" ||
                           s.StoryDevStage == "AwaitingReTestbyCustomer";

                case "ReadyForRegressionTesting":
                case "InRegressionTesting":
                case "ReadyForSAT":
                case "InSAT":
                    return s.StoryDevStage == "ReadyForRegressionTesting" ||
                           s.StoryDevStage == "InRegressionTesting" ||
                           s.StoryDevStage == "ReadyForSAT" ||
                           s.StoryDevStage == "InSAT";

                case "Closed":
                case "Deleted":
                    return s.StoryDevStage == "Closed" ||
                           s.StoryDevStage == "Deleted";
            }

            return false;
        }

        public List<CardWallInstanceOnADate> GetCardWallInstancesBetween(DateTime startDate, DateTime endDate)
        {
            startDate = startDate.Date;
            endDate = endDate.Date;

            AppData appData = GetAppData();
            List<CardWallInstanceOnADate> cardWallInstances =
                appData.ListOfCardWallInstanceOnADate.FindAll(x => x.Date >= startDate && x.Date <= endDate);

            cardWallInstances.ForEach(
                wall =>
                wall.StorySignUps.ForEach(
                    s => { s.Card = appData.ProjectCards.First(p => p.CardNumber == s.CardNumber); }));
            return cardWallInstances;
        }

        public CardWallInstanceOnADate CopyWallInstance(DateTime from, DateTime to)
        {
            from = from.Date;
            to = to.Date;

            AppData appData = GetAppData();

            appData.ListOfCardWallInstanceOnADate.RemoveAll(w => w.Date == to);

            CardWallInstanceOnADate fromWall = null;
            int counter = 0;
            while (fromWall == null || fromWall.StorySignUps.Count == 0)
            {
                fromWall = appData.ListOfCardWallInstanceOnADate.FirstOrDefault(w => w.Date == from);
                from = from.AddDays(-1);
                if (counter == 100)
                    return null;
            }

            var toWall = new CardWallInstanceOnADate(fromWall) {Date = to};
            appData.ListOfCardWallInstanceOnADate.Add(toWall);
            SaveData_Deprecated(appData);


            toWall.StorySignUps.ForEach(s =>
                                            {
                                                s.Card = appData.ProjectCards.First(p => p.CardNumber == s.CardNumber);
                                                s.Card.TotalNumberOfSignUps = appData.ListOfCardWallInstanceOnADate
                                                    .FindAll(w => w.Date != DateTime.Today)
                                                    .SelectMany(w => w.StorySignUps)
                                                    .Count(
                                                        signups =>
                                                        signups.CardNumber == s.CardNumber &&
                                                        signups.Assignees.Count > 0);
                                            });
            return toWall;
        }

        public void SetStandupTime(DateTime date, TimeSpan standupTime)
        {
            date = date.Date;
            AppData appData = GetAppData();
            CardWallInstanceOnADate wallInstance =
                appData.ListOfCardWallInstanceOnADate.FirstOrDefault(w => w.Date == date);
            if (wallInstance != null)
            {
                wallInstance.StandupTimeInSeconds = (int) standupTime.TotalSeconds;
                SaveData_Deprecated(appData);
            }
        }

        #endregion

        #region Story sign ups

        public void AddProjectCardOnWallInstanceForDate(DateTime date, int cardNumber, double cardX, double cardY,
                                                        string storyStatus, string wall, string cardName = "")
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWall = appData.ListOfCardWallInstanceOnADate.FirstOrDefault(x => x.Date == date);

            if (cardWall == null)
                cardWall = new CardWallInstanceOnADate {Date = date};

            StorySignUp storySignUp = cardWall.StorySignUps.FirstOrDefault(s => s.CardNumber == cardNumber);
            if (storySignUp == null)
            {
                storySignUp = new StorySignUp
                                  {
                                      CardNumber = cardNumber,
                                      CardPositionX = cardX,
                                      CardPositionY = cardY,
                                      StoryDevStage = storyStatus,
                                      Wall = wall
                                  };
                cardWall.StorySignUps.Add(storySignUp);
            }
            else
            {
                storySignUp.CardPositionX = cardX;
                storySignUp.CardPositionY = cardY;
                storySignUp.StoryDevStage = storyStatus;
                storySignUp.Wall = wall;
            }
            SaveData_Deprecated(appData);
        }

        public void RemoveCardSignUpFromWall(DateTime date, int cardNumber)
        {
            AppData appData = GetAppData();
            CardWallInstanceOnADate wallInstance = appData.ListOfCardWallInstanceOnADate.Find(w => w.Date == date.Date);
            wallInstance.StorySignUps.RemoveAll(s => s.CardNumber == cardNumber);
            SaveData_Deprecated(appData);
        }

        public void AttachTeamMemberToProjectCard(DateTime date, int projectCardNumber, Guid teamMemberId,
                                                  double teamMemberCardX, double teamMemberCardY)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWallInstance =
                appData.ListOfCardWallInstanceOnADate.FirstOrDefault(c => c.Date == date);
            if (cardWallInstance == null)
                throw new Exception("Card Wall Instance does not exist for date: " + date);

            cardWallInstance.StorySignUps.ForEach(s => s.Assignees.RemoveAll(a => a.Id == teamMemberId));

            var teamMemberSummary = new TeamMemberSummary
                                        {Id = teamMemberId, PositionX = teamMemberCardX, PositionY = teamMemberCardY};
            StorySignUp storySignUp =
                cardWallInstance.StorySignUps.FirstOrDefault(s => s.CardNumber == projectCardNumber);
            if (storySignUp == null)
                throw new Exception("Sign up does not exist to attach team member card");

            storySignUp.Assignees.Add(teamMemberSummary);
            SaveData_Deprecated(appData);
        }

        public int DetachTeamMemberFromAllProjectCards(DateTime date, Guid teamMemberId)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWallInstance =
                appData.ListOfCardWallInstanceOnADate.FirstOrDefault(c => c.Date == date);
            if (cardWallInstance == null)
                throw new Exception("Card Wall Instance does not exist for date: " + date);

            StorySignUp storySignUp =
                cardWallInstance.StorySignUps.FirstOrDefault(s => s.Assignees.Any(a => a.Id == teamMemberId));
            if (storySignUp != null)
            {
                storySignUp.Assignees.RemoveAll(a => a.Id == teamMemberId);
                SaveData_Deprecated(appData);
                return storySignUp.CardNumber;
            }
            return 0;
        }

        public void AttachSecondaryProjectCardToProjectCard(DateTime date, int primaryProjectCardNumber,
                                                            int secondaryProjectCardNumber, double projectCardX,
                                                            double projectCardY)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWallInstance =
                appData.ListOfCardWallInstanceOnADate.FirstOrDefault(c => c.Date == date);
            if (cardWallInstance == null)
                throw new Exception("Card Wall Instance does not exist for date: " + date);

            cardWallInstance.StorySignUps.ForEach(
                s => s.SecondaryCards.RemoveAll(sec => sec == secondaryProjectCardNumber));

            StorySignUp secondaryStorySignUp =
                cardWallInstance.StorySignUps.FirstOrDefault(s => s.CardNumber == secondaryProjectCardNumber);
            if (secondaryStorySignUp == null)
                throw new Exception("Primary card sign up does not exist");
            secondaryStorySignUp.CardPositionX = projectCardX;
            secondaryStorySignUp.CardPositionY = projectCardY;

            StorySignUp primaryStorySignUp =
                cardWallInstance.StorySignUps.FirstOrDefault(s => s.CardNumber == primaryProjectCardNumber);
            if (primaryStorySignUp == null)
                throw new Exception("Primary card sign up does not exist");

            primaryStorySignUp.SecondaryCards.Add(secondaryProjectCardNumber);
            SaveData_Deprecated(appData);
        }

        public int DetachProjectCardFromAllProjectCards(DateTime date, int projectCardNumber)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate cardWallInstance =
                appData.ListOfCardWallInstanceOnADate.FirstOrDefault(c => c.Date == date);
            if (cardWallInstance == null)
                throw new Exception("Card Wall Instance does not exist for date: " + date);

            StorySignUp storySignUp =
                cardWallInstance.StorySignUps.FirstOrDefault(s => s.SecondaryCards.Any(sec => sec == projectCardNumber));
            if (storySignUp != null)
            {
                storySignUp.SecondaryCards.RemoveAll(sec => sec == projectCardNumber);
                SaveData_Deprecated(appData);
                return storySignUp.CardNumber;
            }
            return 0;
        }

        public void UpdateNoteForStorySignUp(int cardNumber, DateTime date, string note)
        {
            date = date.Date;
            AppData appData = GetAppData();

            CardWallInstanceOnADate wallInstance = appData.ListOfCardWallInstanceOnADate.Find(w => w.Date == date);
            StorySignUp storySignUp = wallInstance.StorySignUps.Find(s => s.CardNumber == cardNumber);

            if (storySignUp.Note == note)
                return;

            storySignUp.Note = note;
            SaveData_Deprecated(appData);
        }

        #endregion

        #region Settings

        public Settings GetSettings()
        {
            AppData appData = GetAppData();
            return appData.Settings ?? new Settings();
        }

        public void SaveSettings(Settings settings)
        {
            AppData appData = GetAppData();
            appData.Settings = settings;
            SaveData_Deprecated(appData);
        }

        #endregion
    }
}
